#include <arch/cpu/soc/s3c2440.h>
#include <arch/arm.h>
#include <arch/io.h>
#include <config.h>
#include <clk.h>
#include <errno.h>
#include <div64.h>
#include <util.h>
#include <init.h>

#ifdef __TOP_HALF__

#if (CONFIG_CLKIN_FREQ == 12000000)
#  if (CONFIG_FCLK_FREQ == 304000000)
#    define MPLL_MDIV 0x44
#    define MPLL_PDIV 1
#    define MPLL_SDIV 1
#  elif (CONFIG_FCLK_FREQ == 405000000)
#    define MPLL_MDIV 0x7f
#    define MPLL_PDIV 2
#    define MPLL_SDIV 1
#  endif
#endif

static inline unsigned int get_fclk(void)
{
	return CONFIG_FCLK_FREQ;
}

static inline unsigned int get_hclk(void)
{
	return get_fclk() / CONFIG_FCLK_HCLK_RATIO;
}

static inline unsigned int get_pclk(void)
{
	return get_hclk() / CONFIG_HCLK_PCLK_RATIO;
}

static int clock_get_ctrlbit(unsigned int clk_id)
{
	switch (clk_id) {
		case CLK_NAND:
			return 4;
		case CLK_UART0:
			return 10;
		case CLK_UART1:
			return 11;
		case CLK_UART2:
			return 12;
		case CLK_PWMTIMER:
			return 8;
		case CLK_GPIO:
			return 13;
		default:
			return -1;
	}
}

struct clk *clk_get_by_id(unsigned int clk_id)
{
	return (struct clk *)clk_id;
}

int clk_enable(struct clk *clk)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	int ctrlbit = clock_get_ctrlbit((unsigned int)clk);

	if (ctrlbit < 0)
		return -EINVAL;

	writel(readl(&clk_power->clkcon) | (1 << ctrlbit), &clk_power->clkcon);
	return 0;
}

int clk_disable(struct clk *clk)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	int ctrlbit = clock_get_ctrlbit((unsigned int)clk);

	if (ctrlbit < 0)
		return -EINVAL;

	writel(readl(&clk_power->clkcon) & ~(1 << ctrlbit), &clk_power->clkcon);
	return 0;
}

unsigned int clk_get_rate(struct clk *clk)
{
	unsigned int clk_id = (unsigned int)clk;

	switch (clk_id) {
		case CLK_FCLK:
			return get_fclk();
		case CLK_HCLK:
			return get_hclk();
		case CLK_PCLK:
			return get_pclk();
	}
	return 0;
}

void s3c2440_clocks_init(void)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	uint32_t tmp32;
	struct clk *clk_nand;

	writel(0xffffffff, &clk_power->locktime);	// U_LTIME = M_LTIME = 300us

	writel((MPLL_MDIV << 12) | (MPLL_PDIV << 4) | (MPLL_SDIV << 0), &clk_power->mpllcon);
	
	tmp32  = readl(&clk_power->clkdivn);
	tmp32 &= ~7;
#if (CONFIG_FCLK_HCLK_RATIO == 1) 
	tmp32 |= (0 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 2)
	tmp32 |= (1 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 4) || (CONFIG_FCLK_HCLK_RATIO == 8)
	tmp32 |= (2 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 3) || (CONFIG_FCLK_HCLK_RATIO == 6)
	tmp32 |= (3 << 1);
#else
#error "CONFIG_FCLK_HCLK_RATIO is error!"
#endif
#if (CONFIG_HCLK_PCLK_RATIO == 1)
	tmp32 |= (0 << 0);
#elif (CONFIG_HCLK_PCLK_RATIO == 2)
	tmp32 |= (1 << 0);
#else
#error "CONFIG_HCLK_PCLK_RATIO is error!"
#endif
	writel(tmp32, &clk_power->clkdivn);

	tmp32  = readl(&clk_power->camdivn);
	tmp32 &= ~(3 << 8);
#if (CONFIG_FCLK_HCLK_RATIO == 6)
	tmp32 |= (1 << 8);
#elif (CONFIG_FCLK_HCLK_RATIO == 8)
	tmp32 |= (1 << 9);
#endif
	writel(tmp32, &clk_power->camdivn);

	writel((1 << 4), &clk_power->clkcon); 

	// enable the clock of NAND and disable all other clocks, because of the 4KB SRAM
	clk_nand = clk_get_by_id(CLK_NAND);
	if (IS_ERR(clk_enable(clk_nand))) {
		while (1);
	}

#if (CONFIG_FCLK_HCLK_RATIO == 1) 
	/* fast bus mode */
	set_cr(get_cr() & ~(CR_NF | CR_IA));	
#else 
	/* asynchronous bus mode */
	set_cr(get_cr() | CR_NF | CR_IA);
#endif
}

#else 

static int s3c2440_clkcon_enable(struct clk *clk, int enable)
{       
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
    unsigned int clkcon;
        
    clkcon = readl(&clk_power->clkcon);
        
    if (enable)
		clkcon |= (1 << clk->ctrlbit);
    else
		clkcon &= ~(1 << clk->ctrlbit);
        
    /* ensure none of the special function bits set */
    clkcon &= ~((1 << 2) | (1 << 3));
        
	writel(clkcon, &clk_power->clkcon);

    return 0;
}

static int s3c2440_upll_enable(struct clk *clk, int enable)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
    unsigned int clkslow = readl(&clk_power->clkslow);
   // unsigned int orig = clkslow;

    if (enable)
        clkslow &= ~(1 << 7);
    else
        clkslow |= (1 << 7);

	writel(clkslow, &clk_power->clkslow);

/*
    if (enable && (orig & (1 << 7)))
        udelay(300);
*/

    return 0;
}

static struct clk_ops s3c440_def_clk_ops = {
	.enable		= s3c2440_clkcon_enable,
};

static struct clk s3c2440_clocks[] = {
    {
        .name       = "nand",
        .parent     = &clk_h,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 4,
    }, {
        .name       = "lcd",
        .parent     = &clk_h,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 5,
    }, {
        .name       = "usb-host",
        .parent     = &clk_h,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 6,
    }, {
        .name       = "usb-device",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 7,
    }, {
        .name       = "timers",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 8,
    }, {
        .name       = "sdi",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 9,
    }, {
        .name       = "uart0",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 10,
    }, {
        .name       = "uart1",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 11,
    }, {
        .name       = "uart2",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 12,
    }, {
        .name       = "gpio",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 13,
    }, {
        .name       = "rtc",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 14,
    }, {
        .name       = "adc",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 15,
    }, {
        .name       = "i2c",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 16,
    }, {
        .name       = "iis",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 17,
    }, {
        .name       = "spi",
        .parent     = &clk_p,
		.ops		= &s3c440_def_clk_ops,
        .ctrlbit    = 18,
    }, {
		.name		= "camera",
		.parent		= &clk_h,
		.ops		= &s3c440_def_clk_ops,
		.ctrlbit    = 19,
	}, {
		.name		= "ac97",
		.parent		= &clk_p,
		.ops		= &s3c440_def_clk_ops,
		.ctrlbit    = 20,
	}
};

#define S3C24XX_PLL_MDIV_MASK       (0xFF)
#define S3C24XX_PLL_PDIV_MASK       (0x1F)
#define S3C24XX_PLL_SDIV_MASK       (0x3)
#define S3C24XX_PLL_MDIV_SHIFT      (12)
#define S3C24XX_PLL_PDIV_SHIFT      (4)
#define S3C24XX_PLL_SDIV_SHIFT      (0)
    
static unsigned int s3c2440_get_pll(unsigned int pllval,
                       unsigned int baseclk)
{   
    unsigned int mdiv, pdiv, sdiv;
    uint64_t fvco;
    
    mdiv = (pllval >> 12) & 0xff;
    pdiv = (pllval >> 4) & 0x3f;
    sdiv = (pllval >> 0) & 0x3;

    fvco = (uint64_t)baseclk * (mdiv + 8);
    do_div(fvco, (pdiv + 2) << sdiv);

    return (unsigned int)fvco;
}

void s3c2440_setup_clocks(unsigned int xtal)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
    unsigned int clkdiv;
    unsigned int camdiv;
    unsigned int hclk, fclk, pclk;
    int hdiv = 1;
    struct clk *clkp;
    unsigned int clkcon = readl(&clk_power->clkcon);
	int i;

	s3c_register_baseclocks();

	clk_xtal.rate = xtal;
	
    fclk = s3c2440_get_pll(readl(&clk_power->mpllcon), xtal) * 2;

    clkdiv = readl(&clk_power->clkdivn);
    camdiv = readl(&clk_power->camdivn);

    switch (clkdiv & 6) {
    case 0:
        hdiv = 1;
        break;

    case 2:
        hdiv = 2;
        break;

	case 4:
		hdiv = (camdiv & (1 << 9)) ? 8 : 4;
		break;

    case 6:
		hdiv = (camdiv & (1 << 8)) ? 6 : 3;
		break;
    }

    hclk = fclk / hdiv;
    pclk = hclk / ((clkdiv & 1) ? 2 : 1);

    clk_mpll.rate = fclk;
    clk_h.rate = hclk;
    clk_p.rate = pclk;
    clk_f.rate = fclk;
	
    clk_upll.rate = s3c2440_get_pll(readl(&clk_power->upllcon), xtal);
	
	clk_upll.ops->enable = s3c2440_upll_enable;

    clkp = s3c2440_clocks;
    for (i = 0; i < ARRAY_SIZE(s3c2440_clocks); i++, clkp++) {
        clkp->usage = (clkcon & clkp->ctrlbit) ? 1 : 0;
		s3c_register_clock(clkp);
    }
}

int s3c2440_clock_init(void) 
{
	s3c2440_setup_clocks(12000000);
	return 0;
}
early0_initcall(s3c2440_clock_init);

#endif
